package com.zshy.base.web.advice;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.zshy.base.core.context.ThreadContext;
import com.zshy.base.core.contract.R;
import com.zshy.base.core.contract.constant.ContextConstants;
import com.zshy.base.core.contract.constant.HttpStatusConstants;
import com.zshy.base.core.contract.enums.ResultCodeEnum;
import com.zshy.base.core.contract.exception.ServiceException;
import com.zshy.base.core.contract.exception.ValidateException;
import com.zshy.base.core.utils.ExceptionKit;
import com.zshy.base.core.utils.JsonKit;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.yaml.snakeyaml.constructor.DuplicateKeyException;

import java.time.DateTimeException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 异常处理
 *
 * @author zhouhengzhe
 */
@Slf4j
public class BaseGlobalExceptionAdvice {

    @ExceptionHandler({BindException.class})
    public R<String> bindException(HttpServletRequest request, Model model, BindException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        List<String> errList = e.getFieldErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());
        log.error("请求参数校验失败：{} ，{}，异常错误:{}", String.join(",", errList), model, projectStackTrace);
        return R.fail(HttpStatusConstants.BAD_REQUEST, String.join(",", errList), null);
    }

    @ExceptionHandler({ValidateException.class})
    public R<String> validatorException(HttpServletRequest request, ValidateException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("自定义请求参数校验失败：{}\n**StackTrace:** {}", e.getMessage(), projectStackTrace);
        return R.fail(HttpStatusConstants.BAD_REQUEST, e.getMessage(), null);
    }

    @ExceptionHandler({ValidationException.class})
    public R<String> validationException(HttpServletRequest request, ValidationException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("请求参数校验失败：{}\n**StackTrace:** {}", e.getMessage(), projectStackTrace,e);
        return R.fail(HttpStatusConstants.BAD_REQUEST, "请求参数错误", null);
    }

    @ExceptionHandler({MethodArgumentNotValidException.class})
    public R<String> methodArgumentNotValidExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
        FieldError fieldError = e.getBindingResult().getFieldError();
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("请求参数校验失败：{}\n**StackTraces:** {}", fieldError.getDefaultMessage(), projectStackTrace);
        return R.fail(HttpStatusConstants.BAD_REQUEST, fieldError.getDefaultMessage(), null);
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public R<String> duplicate(HttpServletRequest req, DuplicateKeyException e) {
        log.error("", e);
        return R.fail(HttpStatusConstants.BAD_REQUEST, "重复提交数据！", e.getMessage());
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public R<String> handle(HttpServletRequest request, NoHandlerFoundException e) {
        log.error("地址错误！！！,{} 非法访问!", request.getRequestURI(),e);
        return R.fail(HttpStatusConstants.NOT_FOUND, ResultCodeEnum.URL_NOT_FOUND.getDesc());
    }

    @ExceptionHandler({ServiceException.class})
    public R<Map<String, String>> serviceException(HttpServletRequest request, ServiceException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);


        log.warn("服务异常：{}\nContext：{}\n@See {}", e.getMessage(), JsonKit.toJson(ThreadContext.getValues()), projectStackTrace);
        return R.fail(e.getCode(), e.getMessage(), null);
    }

    @ExceptionHandler({Exception.class})
    public R<String> exception(HttpServletRequest request, Exception e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("运行时异常：{}\n**StackTraces:** {}", e.getMessage(), projectStackTrace);
        return R.fail(HttpStatusConstants.ERROR, e.getMessage(), null);
    }

    /**
     * 校验URL?param=value，参数key是否缺失
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public R<String> parameterMissingHandler(MissingServletRequestParameterException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("请求参数确实异常：{}\n**StackTraces:** {}", e.getMessage(), projectStackTrace);
        return R.fail(HttpStatusConstants.ERROR, e.getMessage(), "请求参数 " + e.getParameterName() + " 不能为空");
    }

    @ExceptionHandler({ConstraintViolationException.class})
    public R<String> otherExceptionHandler(ConstraintViolationException e) {
        // 判断异常中是否有错误信息，如果存在就使用异常中的消息，否则使用默认消息
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("参数校验异常：{}\n**StackTraces:** {}", e.getMessage(), projectStackTrace);
        if (!StringUtils.isEmpty(e.getMessage())) {
            return R.fail(e.getMessage());
        }
        return R.fail(ResultCodeEnum.SERVER_ERROR);
    }

    /**
     * 缺少请求体异常处理器
     * @param e 缺少请求体异常
     * @return Result
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public R<String> parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("缺少请求体异常：{}\n**StackTraces:** {}", e.getMessage(), projectStackTrace);
        Throwable rootCause = e.getRootCause();
        //校验参数是否多余，导致不能识别
        if (rootCause instanceof JsonProcessingException) {
            JsonMappingException mappingException = (JsonMappingException) rootCause;
            String fieldName = mappingException.getPath().get(0).getFieldName();
            return R.fail("参数 " + fieldName + " 不能识别");
        }
        //校验日期字段转换是否异常
        if (rootCause instanceof DateTimeException) {
            return R.fail("日期转换异常");
        }
        //如果都不属于，则属于请求体缺失
        return R.fail("请求体缺失");
    }


    /*
     * 捕获请求方法异常，比如post接口使用了get
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R<String> methodNotAllowedHandler(HttpRequestMethodNotSupportedException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("请求方法不被允许：{}\n**StackTraces:** {}", e.getMessage(), projectStackTrace);
        String method = e.getMethod();
        return R.fail(method + "请求方法不被允许");
    }
}
